package com.hc.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hc.dao.IUUserDAO;
import com.hc.dao.IUserLoginDAO;
import com.hc.entity.UUser;
import com.hc.entity.UUserLogin;
import com.hc.exception.IllegalTokenException;
import com.hc.exception.WrongPasswordException;
import com.hc.service.IAuthorizationService;
import com.hc.util.Constant;
import com.hc.util.JWTUtil;
import com.hc.vo.GetAddressAMapVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import sun.net.util.IPAddressUtil;

import javax.annotation.Resource;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

@Service
public class AuthorizationServiceImpl implements IAuthorizationService {

    //其中放入 userName - UUID 的键值对
    // userName意味着登录用户 UUID意味着占用这个身份的客户端
    @Resource
    RedisTemplate<String, String> redisTemplate;

    @Resource
    IUserLoginDAO userLoginDAO;

    @Resource
    IUUserDAO userDAO;

    @Override
    public String doLogin(String userName, String password, String ip) {
        if (forceValid()) {
            return randomToken();
        }
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, password);
        Subject userSubject = SecurityUtils.getSubject();
        try {
            //尝试利用数据库登录
            userSubject.login(usernamePasswordToken);
            //存入 用户名/token 值
            String subject = UUID.randomUUID().toString();
            String token = JWTUtil.createToken(userName, password, subject,Constant.JWT_EXPIRE_TIME_MILLIS);
            //令牌必须在redis key 过期前REFRESH_THRESHOLD_MILLIS毫秒过期
            redisTemplate.opsForValue().set(userName, subject, Constant.JWT_EXPIRE_TIME_MILLIS + Constant.REFRESH_THRESHOLD_MILLIS + 10, TimeUnit.MILLISECONDS);
//            queryAMapExecutor.schedule(new QueryAMapTimerTask(ip, userLoginDAO, userName, Timestamp.from(new Date().toInstant())), 1, TimeUnit.SECONDS);
            return token;
        } catch (AuthenticationException e) {
            throw new WrongPasswordException();
        }
    }

    /**
     * 你要是给他改成 true , 那么不管什么人都可以登上去
     *
     * @return T/F
     */
    private boolean forceValid() {
        return false;
    }

    private String randomToken() {
        return UUID.randomUUID().toString();
    }

    public TokenStatus verifyToken(String token) {
        if (forceValid()) {
            return TokenStatus.VALID;
        }
        UUser user;
        long expireBefore = 0;
        Claims claims;
        //取出必要的信息
        try {
            claims = JWTUtil.getClaimFromToken(token);
        } catch (ExpiredJwtException e) {
            claims = e.getClaims();
        }
        expireBefore = claims.getExpiration().getTime() - System.currentTimeMillis();
        user = JWTUtil.getUserFromClaims(claims);
        //  过期前5分钟刷新,过期后5分钟也刷新,但是令牌必须在redis key 过期前5分钟过期 不然 redis没有
        //redis里头没有那就是没登陆
        if (Objects.equals(redisTemplate.hasKey(user.getName()), true)) {
            //第二步验证UUID,即每次登录后给予的唯一标识
            if (!Objects.equals(redisTemplate.opsForValue().get(user.getName()), claims.getSubject())) {
                return TokenStatus.INVALID;
            }
            //剩余时间较少,且处在刷新阈值内
            if (Math.abs(expireBefore) <= Constant.REFRESH_THRESHOLD_MILLIS) {
                return TokenStatus.REFRESH;
            } else if (expireBefore < 0) {//你的剩余时间已经没有了,且剩余时间也不在刷新时间限制内
                return TokenStatus.INVALID;
            } else {//剩余时间有的,也不用刷新,说明有效
                return TokenStatus.VALID;
            }
        }
        return TokenStatus.INVALID;
    }

    public String refreshToken(String token) {
        //这个重新签发会保留现存的uuid
        String newToken = JWTUtil.reassignToken(token);
        Claims claims;
        try {
            claims = JWTUtil.getClaimFromToken(token);
        } catch (ExpiredJwtException e) {
            claims = e.getClaims();
        }
        //这边也是用新的UUID存入
        redisTemplate.opsForValue().set(JWTUtil.getUserFromClaims(claims).getName(), claims.getSubject(), Constant.JWT_EXPIRE_TIME_MILLIS * 2, TimeUnit.MILLISECONDS);
        return newToken;
    }


    public static final Logger LOGGING_LOGGER = Logger.getLogger("userLogging");

    ScheduledThreadPoolExecutor queryAMapExecutor = new ScheduledThreadPoolExecutor(10);

    /**
     * 请求高德地图并写入数据库
     */
    static class QueryAMapTimerTask extends TimerTask {

        String ip;

        IUserLoginDAO dao;

        String userName;

        Timestamp loginTime;

        public QueryAMapTimerTask(String ip, IUserLoginDAO dao, String userName, Timestamp loginTime) {
            this.ip = ip;
            this.dao = dao;
            this.userName = userName;
            this.loginTime = loginTime;
        }

        static ObjectMapper mapper = new ObjectMapper();

        @Override
        public void run() {
            if (!IPAddressUtil.isIPv4LiteralAddress(ip)) {
                LOGGING_LOGGER.log(Level.WARNING, "传入错误的IP" + ip);
            }
            try {
                URL url = new URL("https://restapi.amap.com/v3/ip?key=b2b9934d08cd6ffc59e1d54a0bc0799a&ip=58.212.180.76");
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                if (connection.getResponseCode() != 200) {
                    LOGGING_LOGGER.log(Level.WARNING, "高德服务器返回:" + connection.getResponseCode());
                    return;
                }
                String resp = IOUtils.toString(connection.getInputStream(), "UTF8");
                String respU8 = new String(resp.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
                GetAddressAMapVO addressVO = mapper.readValue(respU8, GetAddressAMapVO.class);
                if (addressVO.getStatus().equals("0")) {
                    LOGGING_LOGGER.log(Level.WARNING, "高德接口请求失败,原因:" + addressVO.getInfo() + ",infoCode" + addressVO.getInfocode());
                }
                UUserLogin userLogin = new UUserLogin();
                String time = String.valueOf(System.currentTimeMillis());
                userLogin.setLoginId(time);
                userLogin.setUserId(time);
                userLogin.setUserName(userName);
                String sb = "省:" +
                        addressVO.getProvince() +
                        ",市:" +
                        addressVO.getCity();
                userLogin.setLoginLocation(sb);
                userLogin.setSource("WEB");
                userLogin.setLoginTime(loginTime);
                dao.addLoginRecord(userLogin);
            } catch (Exception e) {
                LOGGING_LOGGER.log(Level.SEVERE, "请求失败");
                e.printStackTrace();
            }
        }
    }


    @Override
    public String getCommunityId(String token) {
        String result = userDAO.getCommunityIdByName(JWTUtil.getUserFromToken(token).getName());
        if (result == null) {
            throw new IllegalTokenException();
        }
        return result;
    }
}
